//: [Previous](@previous)
import Foundation
//: ## 下标 (subscript)
/**
 使用subscript下标, 可以给任意类型(枚举, 结构体, 类)增加下标功能, 有些地方会翻译成:小标脚本
 - subscript的语法类似于 实例方法 / 计算属性, 本质其实就是方法(函数)
 - 实际作用有点类似想给这个类型增加一些类似数组或者字典来取值的操作, 就可以用到下标
 
 subscript中定义的返回值决定了:
 1. get方法的返回值类型
 2. set方法中newValue的类型
 就是说subscript的返回值的类型,就是p[0] = 的类型,也是 p[0]的类型
 
 subscript可以接受多个参数, 并且是任何类型, 比如整形, 字符串 ......
 */
class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        set {
            if index == 0 {
                x = newValue
            } else if index == 1 {
                y = newValue
            }
        }
        
        get {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
}
var p1 = Point()
p1[0] = 1.0
p1[1] = 2.0
print(p1[0]) //1.0
print(p1.x) //1.0 2个访问的内存是一样
//: ## 下标的细节
// subscript可以没有set方法, 不能没有get方法, 和计算属性一样 ,说白了这种情况就是 只读
class Point1 { //这种没有set的情况下, 就是只读, 不能对p[0]赋值
    var x = 0, y = 0
    subscript(index: Int) -> Int {
        get{
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
}

// 如果只有get方法, 可以省略get, Point1和Point2 实现是一样的
class Point2 {
    var x = 0, y = 0
    subscript(index: Int) -> Int {
        if index == 0 {
            return x
        } else if index == 1 {
            return y
        }
        return 0
    }
}

// 下标可以设置参数标签
class Point3 {
    var x = 0, y = 0
    subscript(aa bb: Int) -> Int {
        if bb == 0 {
            return x
        } else if bb == 1 {
            return y
        }
        return 0
    }
}
var p3 = Point3()
p3.x = 1
print(p3[aa: 0])// aa 和 bb, 之前的写法是只有一个index, 系统就会自动把index当做参数, 输出的时候不用写, 现在有2个标签aa和bb, 前者的aa就是标签, 后者bb就是下标
//: ### 下标可以是 类型方法, 可以直接通过类型来调用
// [重要][重要][重要]下标可以是类型方法, 可以直接通过类型来调用
class Sum {
    static subscript(v1: Int, v2: Int) -> Int {
        return v1 + v2
    }
}
print(Sum[10, 20]) //30
//: ## 结构体, 类 作为返回值对比
struct Point4 {
    var x = 10, y = 20
}
class Point4Manager {
    var point = Point4()
    subscript(index: Int) -> Point4 {
        set {
            point = newValue
        }
        get {
            return point
        }
    }
}
var p4 = Point4Manager()
print(p4.point.x)
print(p4[0].y) // 因为Point4Manager的subscript方法中index并没有用到, 所以不管[]里面传入什么数字, 都可以触发set 和 get 方法
p4[1].x = 110
p4[1] = Point4(x:p4[1].x, y: 220) //上面的写法p4[1].x = 110 其实就是系统默认补全了, 其实就是调用了set方法, 右边其实就是newValue
print(p4[1].x)
// 因为Point4是结构体, 结构体是值类型, 所以要p4[1].x = 11直接赋值, 就必须要实现Point4Manager里面的set方法. 但是假如Point4是类, 因为类是引用类型也叫指针变量, p4[1]此时放回的是引用类型,是地址,相当于get拿到一个地址,地址里面的内容是引用类型, 所以就可以直接修改赋值不会报错,因为地址没有变化. 所以类可以不用加上set方法. 相反要注意假如是类, 还实现了set方法,就相当于把这个var point重新赋值了一个新的类, 是一个新的地址了, 而不是修改原先的类上地址所指的内容
// 总结: 下标的返回值,当返回值是类class的时候,可以不写set方法去赋值.但是假如返回值是结构体struct,需要赋值的话就需要写上set方法  
//: ## 接收多个参数的下标
class Grid {// 网格需求
    var data = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8]
    ]// 这是一个二维数组, 一个数组里面装着3个数组
    subscript(row: Int, column: Int) -> Int {// row:第几行, column: 第几列, 传入行和列数后返回对应的数
        set {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return
            }
            data[row][column] = newValue
        }
        get {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {
                return 0
            }//guard语句满足条件,就直接跳出大括号, 反之执行大括号里面的内容
            return data[row][column]
        }
    }
}
var g1 = Grid()
g1[1, 1] //4 第2行,第2列, 0开始计数
g1[1, 1] = 44
print(g1[1, 1]) // 44
print(g1.data)









//: [Next](@next)
